// Copyright (c) 2010-2011 SameGoal LLC.
// All Rights Reserved.
// Author: Andy Hochhaus <ahochhaus@samegoal.com>

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "protobuf/js/code_generator.h"

#include <string>
#include <iostream>  // NOLINT
#include <sstream>  // NOLINT

#include "google/protobuf/descriptor.h"
#include "google/protobuf/io/printer.h"
#include "google/protobuf/io/zero_copy_stream.h"
#include "google/protobuf/stubs/common.h"

#include "protobuf/js/int64_encoding.pb.h"
#include "protobuf/js/javascript_package.pb.h"

namespace sg {
namespace protobuf {
namespace js {

CodeGenerator::CodeGenerator(const std::string &name)
    : name_(name) {}

CodeGenerator::~CodeGenerator() {}

bool CodeGenerator::Generate(
    const google::protobuf::FileDescriptor *file,
    const std::string &/* parameter */,
    google::protobuf::compiler::OutputDirectory *output_directory,
    std::string *error) const {

  const std::string file_name = file->name();
  std::string output_file_name = file->name();
  std::size_t loc = output_file_name.rfind(".");
  output_file_name.erase(loc, output_file_name.length() - loc);
  output_file_name.append(".pb.js");

  google::protobuf::internal::scoped_ptr<
      google::protobuf::io::ZeroCopyOutputStream> output(
          output_directory->Open(output_file_name));
  google::protobuf::io::Printer printer(output.get(), '$');
  printer.Print(
      "// Generated by the protocol buffer compiler.  DO NOT EDIT!\n");
  printer.Print("// source: $file_name$\n", "file_name", file_name);
  printer.Print("\n");
  printer.Print(
      "/**\n"
      " * @fileoverview Generated Protocol Buffer code for file $file_name$.\n"
      " */\n", "file_name", file_name);
  printer.Print("\n");

  // goog.provide all messages and enums
  for (int i = 0; i < file->message_type_count(); ++i) {
    CodeGenerator::GenDescriptorGoogProvides(
        file->message_type(i),
        &printer);
  }
  for (int i = 0; i < file->enum_type_count(); ++i) {
    CodeGenerator::GenEnumDescriptorGoogProvides(
        file->enum_type(i),
        &printer);
  }

  printer.Print("\n");
  printer.Print("goog.require('goog.proto2.Message');\n"
                "\n");
  for (int i = 0; i < file->dependency_count(); ++i) {
    for (int j = 0; j < file->dependency(i)->message_type_count(); j++) {
      printer.Print(
          "goog.require('$file$');\n",
          "file",
          JsFullName(file->dependency(i)->message_type(j)->file(),
                     file->dependency(i)->message_type(j)->full_name()));
    }
    for (int j = 0; j < file->dependency(i)->enum_type_count(); j++) {
      const std::string enum_name = JsFullName(
          file->dependency(i)->enum_type(j)->file(),
          file->dependency(i)->enum_type(j)->full_name());
      if (enum_name == "Int64Encoding") {
        // The Int64Encoding enum is special in that it is not used directly
        // by any of the protobuf messages, instead it is only used internally
        // by the plugins to determine how to encode JS numbers. Therefore, the
        // generated *.pb.js file should not goog.require Int64Encoding.
        continue;
      }
      printer.Print("goog.require('$file$');\n", "file", enum_name);
    }
  }

  printer.Print("\n");

  // generate accessor functions
  for (int i = 0; i < file->message_type_count(); ++i) {
    CodeGenerator::GenDescriptor(
        file->message_type(i),
        &printer);
  }
  for (int i = 0; i < file->enum_type_count(); ++i) {
    CodeGenerator::GenEnumDescriptor(
        file->enum_type(i),
        &printer);
  }

  printer.Print("\n");

  // generate metadata
  for (int i = 0; i < file->message_type_count(); ++i) {
    CodeGenerator::GenDescriptorMetadata(
        file->message_type(i),
        &printer);
  }

  if (printer.failed()) {
    *error = "CodeGenerator detected write error.";
    return false;
  }

  return true;
}

std::string CodeGenerator::JsFullName(
    const google::protobuf::FileDescriptor *file,
    const std::string &full_name) {
  std::string new_name = full_name;
  const std::string package = file->package();

  std::string prefix = file->options().GetExtension(javascript_package);
  if (prefix.length() > 0) {
    if (package.length() > 0) {
      new_name.erase(0, package.length() + 1);
    }
    new_name = prefix + "." + new_name;
  }
  return new_name;
}

void CodeGenerator::GenDescriptorGoogProvides(
    const google::protobuf::Descriptor *message,
    google::protobuf::io::Printer *printer) {
  printer->Print("goog.provide('$name$');\n",
                 "name", JsFullName(message->file(),
                                    message->full_name()));

  // enums
  for (int i = 0; i < message->enum_type_count(); ++i) {
    CodeGenerator::GenEnumDescriptorGoogProvides(
        message->enum_type(i),
        printer);
  }

  // Recursively process nested messages
  for (int i = 0; i < message->nested_type_count(); ++i) {
    CodeGenerator::GenDescriptorGoogProvides(
        message->nested_type(i),
        printer);
  }
}

void CodeGenerator::GenEnumDescriptorGoogProvides(
    const google::protobuf::EnumDescriptor *enum_desc,
    google::protobuf::io::Printer *printer) {
  printer->Print("goog.provide('$name$');\n",
                 "name", JsFullName(enum_desc->file(),
                                    enum_desc->full_name()));
}

void CodeGenerator::GenDescriptor(
    const google::protobuf::Descriptor *message,
    google::protobuf::io::Printer *printer) {
  printer->Print("\n"
                 "/**\n"
                 " * Message $name$.\n"
                 " * @constructor\n"
                 " * @extends {goog.proto2.Message}\n"
                 " */\n",
                 "name", message->name());
  printer->Print("$name$ = function() {\n",
                 "name", JsFullName(message->file(),
                                    message->full_name()));
  printer->Indent();
  printer->Print("goog.proto2.Message.apply(this);\n");
  printer->Outdent();
  printer->Print("};\n"
                 "goog.inherits($name$, goog.proto2.Message);\n"
                 "\n"
                 "\n",
                 "name", JsFullName(message->file(),
                                    message->full_name()));

  printer->Print(
      "/**\n"
      " * Overrides {@link goog.proto2.Message#clone} to specify its exact "
      "return type.\n"
      " * @return {!$name$} The cloned message.\n"
      " * @override\n"
      " */\n"
      "$name$.prototype.clone;\n",
      "name", JsFullName(message->file(),
                         message->full_name()));

  // fields
  for (int i = 0; i < message->field_count(); ++i) {
    CodeGenerator::GenFieldDescriptor(
        message->field(i),
        printer);
  }

  // enums
  for (int i = 0; i < message->enum_type_count(); ++i) {
    CodeGenerator::GenEnumDescriptor(
        message->enum_type(i),
        printer);
  }

  // nested messages (recursively process)
  for (int i = 0; i < message->nested_type_count(); ++i) {
    CodeGenerator::GenDescriptor(
        message->nested_type(i),
        printer);
    printer->Print("\n"
                   "\n");
  }
}

void CodeGenerator::GenFieldDescriptor(
    const google::protobuf::FieldDescriptor *field,
    google::protobuf::io::Printer *printer) {
  printer->Print("\n");
  std::string type;
  bool type_is_primitive = false;
  bool as_number = field->options().GetExtension(jstype);
  if (field->type() == google::protobuf::FieldDescriptor::TYPE_BOOL) {
    type = "boolean";
    type_is_primitive = true;
  } else if (
      field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES ||
      field->type() == google::protobuf::FieldDescriptor::TYPE_STRING ||
      ((field->type() == google::protobuf::FieldDescriptor::TYPE_INT64 ||
        field->type() == google::protobuf::FieldDescriptor::TYPE_UINT64 ||
        field->type() == google::protobuf::FieldDescriptor::TYPE_SINT64 ||
        field->type() == google::protobuf::FieldDescriptor::TYPE_FIXED64 ||
        field->type() == google::protobuf::FieldDescriptor::TYPE_SFIXED64) &&
       !as_number)) {
    type = "string";
    type_is_primitive = true;
  } else if (
      field->type() == google::protobuf::FieldDescriptor::TYPE_GROUP ||
      field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) {
    type = JsFullName(field->message_type()->file(),
                      field->message_type()->full_name());
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) {
    type = JsFullName(field->enum_type()->file(),
                      field->enum_type()->full_name());
    type_is_primitive = true;
  } else {
    type = "number";
    type_is_primitive = true;
  }

  std::ostringstream number;
  number << field->number();

  std::string upper_name = field->camelcase_name();
  if (upper_name[0] >= 97 && upper_name[0] <= 122) {
    upper_name[0] -= 32;
  }

  // get
  if (field->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED) {
    printer->Print(
        "\n"
        "/**\n"
        " * Gets the value of the $name$ field at the index given.\n"
        " * @param {number} index The index to lookup.\n",
        "name", field->name());
    printer->Print(
        " * @return {$opt$$type$} The value.\n"
        " */\n",
        "opt", type_is_primitive ? "?" : "",
        "type", type);
    printer->Print(
        "$prefix$.prototype.get$field$ = function(index) {\n",
        "prefix", JsFullName(field->containing_type()->file(),
                             field->containing_type()->full_name()),
        "field", upper_name);
    printer->Indent();
    printer->Print(
        "return /** @type {$opt$$type$} */ ",
        "opt", type_is_primitive ? "?" : "",
        "type", type);
    printer->Print(
        "(this.get$$Value($number$, index));\n",
        "number", number.str());
    printer->Outdent();
    printer->Print(
        "};\n"
        "\n");

    printer->Print(
        "\n"
        "/**\n"
        " * Gets the value of the $name$ field at the index given or the "
            "default value if not set.\n"
        " * @param {number} index The index to lookup.\n",
        "name", field->name());
    printer->Print(
        " * @return {$opt$$type$} The value.\n"
        " */\n",
        "opt", type_is_primitive ? "" : "!",
        "type", type);
    printer->Print(
        "$prefix$.prototype.get$field$OrDefault = function(index) {\n",
        "prefix", JsFullName(field->containing_type()->file(),
                             field->containing_type()->full_name()),
        "field", upper_name);
    printer->Indent();
    printer->Print(
        "return /** @type {$opt$$type$} */ (",
        "opt", type_is_primitive ? "" : "!",
        "type", type);
    printer->Print(
        "this.get$$ValueOrDefault($number$, "
            "index));\n",
        "number", number.str());
    printer->Outdent();
    printer->Print(
        "};\n"
        "\n");
  } else {
    printer->Print(
        "\n"
        "/**\n"
        " * Gets the value of the $name$ field.\n",
        "name", field->name());
    printer->Print(
        " * @return {$opt$$type$} The value.\n"
        " */\n",
        "opt", type_is_primitive ? "?" : "",
        "type", type);
    printer->Print(
        "$prefix$.prototype.get$field$ = function() {\n",
        "prefix", JsFullName(field->containing_type()->file(),
                             field->containing_type()->full_name()),
        "field", upper_name);
    printer->Indent();
    printer->Print(
        "return /** @type {$opt$$type$} */ ",
        "opt", type_is_primitive ? "?" : "",
        "type", type);
    printer->Print(
        "(this.get$$Value($number$));\n",
        "number", number.str());
    printer->Outdent();
    printer->Print(
        "};\n"
        "\n");

    printer->Print(
        "\n"
        "/**\n"
        " * Gets the value of the $name$ field or the default value if not "
        "set.\n",
        "name", field->name());
    printer->Print(
        " * @return {$opt$$type$} The value.\n"
        " */\n",
        "opt", type_is_primitive ? "" : "!",
        "type", type);
    printer->Print(
        "$prefix$.prototype.get$field$OrDefault = function() {\n",
        "prefix", JsFullName(field->containing_type()->file(),
                             field->containing_type()->full_name()),
        "field", upper_name);
    printer->Indent();
    printer->Print(
        "return /** @type {$opt$$type$} */ (",
        "opt", type_is_primitive ? "" : "!",
        "type", type);
    printer->Print(
        "this.get$$ValueOrDefault($number$));\n",
        "number", number.str());
    printer->Outdent();
    printer->Print(
        "};\n"
        "\n");
  }

  // set
  if (field->label() != google::protobuf::FieldDescriptor::LABEL_REPEATED) {
    printer->Print("\n"
                   "/**\n"
                   " * Sets the value of the $name$ field.\n",
                   "name", field->name());
    printer->Print(" * @param {$opt$$type$} value The value.\n"
                   " */\n",
                   "opt", type_is_primitive ? "" : "!",
                   "type", type);
    printer->Print("$prefix$.prototype.set$field$ = function(value) {\n",
                   "prefix", JsFullName(field->containing_type()->file(),
                                        field->containing_type()->full_name()),
                   "field", upper_name);
    printer->Indent();
    printer->Print(
        "this.set$$Value($number$, value);\n",
        "number", number.str());
    printer->Outdent();
    printer->Print("};\n"
                   "\n");
  }

  // add, Array
  if (field->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED) {
    printer->Print("\n"
                   "/**\n"
                   " * Adds a value to the $name$ field.\n",
                   "name", field->name());
    printer->Print(" * @param {$opt$$type$} value The value to add.\n"
                   " */\n",
                   "opt", type_is_primitive ? "" : "!",
                   "type", type);
    printer->Print("$prefix$.prototype.add$field$ = function(value) {\n",
                   "prefix", JsFullName(field->containing_type()->file(),
                                        field->containing_type()->full_name()),
                   "field", upper_name);
    printer->Indent();
    printer->Print(
        "this.add$$Value($number$, value);\n",
        "number", number.str());
    printer->Outdent();
    printer->Print("};\n"
                   "\n");

    printer->Print("\n"
                   "/**\n"
                   " * Returns the array of values in the $name$ field.\n",
                   "name", field->name());
    printer->Print(
        " * @return {!Array.<$opt$$type$>} The values in the field.\n"
        " */\n",
        "opt", type_is_primitive ? "" : "!",
        "type", type);
    printer->Print("$prefix$.prototype.$field$Array = function() {\n",
                   "prefix", JsFullName(field->containing_type()->file(),
                                        field->containing_type()->full_name()),
                   "field", field->camelcase_name());
    printer->Indent();
    printer->Print(
        "return /** @type {!Array.<$opt$$type$>} */ (",
        "type", type,
        "opt", type_is_primitive ? "" : "!");
    printer->Print(
        "this.array$$Values($number$));"
        "\n",
        "number", number.str());
    printer->Outdent();
    printer->Print("};\n"
                   "\n");
  }

  // has
  printer->Print("\n"
                 "/**\n"
                 " * @return {boolean} Whether the $name$ field has a value.\n"
                 " */\n",
                 "name", field->name());
  printer->Print("$prefix$.prototype.has$field$ = function() {\n",
                 "prefix", JsFullName(field->containing_type()->file(),
                                      field->containing_type()->full_name()),
                 "field", upper_name);
  printer->Indent();
  printer->Print("return this.has$$Value($number$);\n",
                 "number", number.str());
  printer->Outdent();
  printer->Print("};\n"
                 "\n");

  // count
  printer->Print(
      "\n"
      "/**\n"
      " * @return {number} The number of values in the $name$ field.\n"
      " */\n",
      "name", field->name());
  printer->Print("$prefix$.prototype.$field$Count = function() {\n",
                 "prefix", JsFullName(field->containing_type()->file(),
                                      field->containing_type()->full_name()),
                 "field", field->camelcase_name());
  printer->Indent();
  printer->Print("return this.count$$Values($number$);\n",
                 "number", number.str());
  printer->Outdent();
  printer->Print("};\n"
                 "\n");

  // clear
  printer->Print("\n"
                 "/**\n"
                 " * Clears the values in the $name$ field.\n"
                 " */\n",
                 "name", field->name());
  printer->Print("$prefix$.prototype.clear$field$ = function() {\n",
                 "prefix", JsFullName(field->containing_type()->file(),
                                      field->containing_type()->full_name()),
                 "field", upper_name);
  printer->Indent();
  printer->Print("this.clear$$Field($number$);\n",
                 "number", number.str());
  printer->Outdent();
  printer->Print("};\n");
}

void CodeGenerator::GenEnumDescriptor(
    const google::protobuf::EnumDescriptor *enum_desc,
    google::protobuf::io::Printer *printer) {
  printer->Print("\n"
                 "\n"
                 "/**\n"
                 " * Enumeration $name$.\n"
                 " * @enum {number}\n"
                 " */\n",
                 "name", enum_desc->name());
  printer->Print("$name$ = {\n",
                 "name", JsFullName(enum_desc->file(),
                                    enum_desc->full_name()));
  printer->Indent();
  for (int i = 0; i < enum_desc->value_count(); ++i) {
    std::string format = "$key$: $value$,\n";
    if (i == enum_desc->value_count() - 1) {
      format = "$key$: $value$\n";
    }
    std::ostringstream number;
    number << enum_desc->value(i)->number();
    printer->Print(format.c_str(),
                   "key", enum_desc->value(i)->name(),
                   "value", number.str());
  }
  printer->Outdent();
  printer->Print("};\n"
                 "\n"
                 "\n");
}

void CodeGenerator::GenDescriptorMetadata(
      const google::protobuf::Descriptor *message,
      google::protobuf::io::Printer *printer) {
  printer->Print("\n"
                 "\n"
                 "goog.proto2.Message.set$$Metadata($name$, {\n",
                 "name", JsFullName(message->file(),
                                    message->full_name()));
  printer->Indent();
  printer->Print("0: {\n");
  printer->Indent();
  printer->Print("name: '$name$',\n",
                 "name", message->name());
  if (message->containing_type() != NULL) {
    printer->Print("containingType: $type$,\n",
                   "type",
                   JsFullName(message->containing_type()->file(),
                              message->containing_type()->full_name()));
  }
  printer->Print("fullName: '$fullname$'\n",
                 "fullname", message->full_name());
  printer->Outdent();
  if (message->field_count() > 0) {
    printer->Print("},\n");
  } else {
    printer->Print("}\n");
  }
  // fields
  for (int i = 0; i < message->field_count(); ++i) {
    CodeGenerator::GenFieldDescriptorMetadata(
        message->field(i),
        printer);
    if (i != message->field_count() - 1) {
      printer->Print(",\n");
    } else {
      printer->Print("\n");
    }
  }
  printer->Outdent();
  printer->Print("});\n");

  // nested messages (recursively process)
  for (int i = 0; i < message->nested_type_count(); ++i) {
    CodeGenerator::GenDescriptorMetadata(
        message->nested_type(i),
        printer);
  }
}

void CodeGenerator::GenFieldDescriptorMetadata(
      const google::protobuf::FieldDescriptor *field,
      google::protobuf::io::Printer *printer) {
  std::ostringstream number;
  number << field->number();

  std::ostringstream default_value;
  std::string js_type;
  bool as_number = field->options().GetExtension(jstype);
  if (field->type() == google::protobuf::FieldDescriptor::TYPE_DOUBLE) {
    js_type = "DOUBLE";
    default_value << field->default_value_double();
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_FLOAT) {
    js_type = "FLOAT";
    default_value << field->default_value_float();
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_INT64) {
    js_type = "INT64";
    if (as_number) {
      default_value << field->default_value_int64();
    } else {
      default_value << "'" << field->default_value_int64() << "'";
    }
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_UINT64) {
    js_type = "UINT64";
    if (as_number) {
      default_value << field->default_value_uint64();
    } else {
      default_value << "'" << field->default_value_uint64() << "'";
    }
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_INT32) {
    js_type = "INT32";
    default_value << field->default_value_uint32();
  } else if (
      field->type() == google::protobuf::FieldDescriptor::TYPE_FIXED64) {
    js_type = "FIXED64";
    if (as_number) {
      default_value << field->default_value_uint64();
    } else {
      default_value << "'" << field->default_value_uint64() << "'";
    }
  } else if (
      field->type() == google::protobuf::FieldDescriptor::TYPE_FIXED32) {
    js_type = "FIXED32";
    default_value << field->default_value_uint32();
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_BOOL) {
    js_type = "BOOL";
    default_value << field->default_value_bool();
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_STRING) {
    js_type = "STRING";
    default_value << "'" << field->default_value_string() << "'";
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_GROUP) {
    js_type = "GROUP";
  } else if (
      field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) {
    js_type = "MESSAGE";
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) {
    js_type = "BYTES";
    default_value << "'" << field->default_value_string() << "'";
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_UINT32) {
    js_type = "UINT32";
    default_value << field->default_value_uint32();
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) {
    js_type = "ENUM";

    default_value << JsFullName(field->enum_type()->file(),
                                field->enum_type()->full_name() + "." +
                                field->default_value_enum()->name());
  } else if (
      field->type() == google::protobuf::FieldDescriptor::TYPE_SFIXED32) {
    js_type = "SFIXED32";
    default_value << field->default_value_int32();
  } else if (
      field->type() == google::protobuf::FieldDescriptor::TYPE_SFIXED64) {
    js_type = "SFIXED64";
    if (as_number) {
      default_value << field->default_value_int64();
    } else {
      default_value << "'" << field->default_value_int64() << "'";
    }
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_SINT32) {
    js_type = "SINT32";
    default_value << field->default_value_int32();
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_SINT64) {
    js_type = "SINT64";
    if (as_number) {
      default_value << field->default_value_int64();
    } else {
      default_value << "'" << field->default_value_int64() << "'";
    }
  }

  std::string js_object;
  if (field->type() == google::protobuf::FieldDescriptor::TYPE_BOOL) {
    js_object = "Boolean";
  } else if (
      field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES ||
      field->type() == google::protobuf::FieldDescriptor::TYPE_STRING ||
      ((field->type() == google::protobuf::FieldDescriptor::TYPE_INT64 ||
        field->type() == google::protobuf::FieldDescriptor::TYPE_UINT64 ||
        field->type() == google::protobuf::FieldDescriptor::TYPE_SINT64 ||
        field->type() == google::protobuf::FieldDescriptor::TYPE_FIXED64 ||
        field->type() == google::protobuf::FieldDescriptor::TYPE_SFIXED64) &&
       !as_number)) {
    js_object = "String";
  } else if (
      field->type() == google::protobuf::FieldDescriptor::TYPE_GROUP ||
      field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) {
    js_object = JsFullName(field->message_type()->file(),
                           field->message_type()->full_name());
  } else if (field->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) {
    js_object = JsFullName(field->enum_type()->file(),
                           field->enum_type()->full_name());
  } else {
    js_object = "Number";
  }

  printer->Print("$number$: {\n",
                 "number", number.str());
  printer->Indent();
  printer->Print("name: '$name$',\n",
                 "name", field->name());
  if (field->is_repeated()) {
    printer->Print("repeated: true,\n");
  }
  printer->Print("fieldType: goog.proto2.Message.FieldType.$js_type$,\n",
                 "js_type", js_type);
  if (field->has_default_value() ||
      field->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) {
    printer->Print("defaultValue: $default$,\n",
                   "default", default_value.str());
  }
  printer->Print("type: $type$\n",
                 "type", js_object);
  printer->Outdent();
  printer->Print("}");
}

}  // namespace js
}  // namespace protobuf
}  // namespace sg
